package com.gjzhao.fmmall.controller;

import com.gjzhao.fmmall.constant.Constants;
import com.gjzhao.fmmall.entity.Orders;
import com.gjzhao.fmmall.service.OrdersService;
import com.gjzhao.fmmall.vo.ResponseResultVO;
import com.gjzhao.fmmall.vo.RespCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description: 订单 Controller
 * @Author: gjzhao
 * @Date Created in 2022-01-09 5:44 下午
 */
@RestController
@RequestMapping("/orders")
@CrossOrigin
@Api(tags = "订单管理", value = "订单处理接口")
public class OrdersController extends BaseController {

    @Autowired
    private OrdersService ordersService;


    /**
     * 添加订单(使用post请求，配合@RequestBody注解使用)
     * 业务流程分析：
     *      - 1.用户在购物车列表页面选择购物车进行提交，携带订单信息(用户id/收货地址/购物车id/支付方式...)
     *      - 2.生成库存
     *          - 2.1 检验库存(根据购物车ids，获取选择的购物车列表信息，遍历购物车，检查购买数量是否大于库存数量)
     *          - 2.2 生成订单信息(初始化订单状态为"待付款")
     *          - 2.3 生成商品快照信息(初始化商品评价状态为"待评价")
     *          - 2.4 扣减库存,计算新的库存
     *          - 2.5 删除购物车商品信息
     *      - 3.向微信支付平台申请支付链接
     *      - 4.给前端返回支付链接，订单编号
     *
     * 分布式场景，问题1：现在有多台应用服务器，使用jvm层面的锁只能锁住当前应用服务器，在高并发场景下，会出现"超卖"现象。
     *       - 比如：用户a在应用服务器1上购买商品，进行添加订单，首先校验库存，发现库存足够，开始生成订单/商品快照等(还未扣减库存)..
     *            此时，用户b在应用服务器2上与用户a购买同一件商品，进行添加订单，首先校验库存，(因为此时用户a的订单还未扣减库存)发现库存足够，开始生成订单/商品快照等(还未扣减库存)..
     *            最后，用户a，b均下单完成，但是库存只扣减了一次，但是却要发两个人的货。
     *
     * 解决办法1：使用redis做分布式锁(注意：redis本身并不是锁，只是使用redis的特性实现分布式锁)
     *      - 流程分析：
     *          - 1.加锁： 在下订单流程之前，以当前skuId为key向redis中添加一条记录(使用redis的setnx特性：如果redis中存在key，则添加失败)
     *          - 2.//业务操作：校验库存、生成订单、生成商品快照、扣减库存、删除购物车信息
     *          - 3.释放锁：业务操作完成后，删除redis中key为skuId的那条记录
     *
     * 问题2：提交的一笔订单中可能存在多个商品，需要对每个商品进行加锁，如果部分商品加锁失败，导致最终加锁状态为"失败"。
     * 解决办法：记录下每个已经加锁的商品skuId，如果加锁失败，释放掉已经上锁的商品
     *
     *
     * 问题3：商品上锁成功后，可以使用上锁前查询的购物车信息，然后进行业务操作吗？
     * - 不能马上进行校验库存操作，高并发场景下，可能在查询购物车信息之后，商品加锁之前，其他用户已经修改了库存，此时需要重新查询购物车信息
     *
     *
     * 问题4：在正常业务操作时，因为某些原因，线程a发生了故障，导致不能正确释放"🔒"。
     * 解决方案：给每个商品的"加锁"添加过期时间，这样就算发生异常，最后也会释放"锁"
     *
     *
     * 问题5：当前操作下，高并发场景中， 正常业务场景中，可能锁还没有释放，已经过期了(此时其他线程可以正常对该商品加锁)，
     *            当做完业务操作需要去释放操作时，释放的锁是另外的线程加的锁
     *            导致其他线程处于无锁状态去进行业务操作。
     * 解决方案：给每个商品上锁时，设置值为一个UUID（UUID重复的概率还是比较低），业务操作完成后，释放锁时需要判断当前释放的锁是否是当前线程加的锁
     *
     *
     * 问题6：业务正常操作完成后进行释放锁时，redis的查询与删除操作并不是一个原子操作，高并发场景下，仍然可能存在a线程释放了b线程的锁
     * 解决办法：引入lua脚本语言，保证redis的查询与删除操作具有原子性
     *
     *
     * 问题7：为了防止在线程异常时，保证锁的正确释放，将锁设置了过期时间，但是锁的过期时间长短并不好进行确认，可能存在业务操作并没有完成，但是锁过期了
     * 解决办法：引入"watch Dog(看门狗机制)"：引入一个守护线程，当业务线程未结束时，redis锁快要过期时，给redis锁进行续命，保证业务操作的正常执行过程
     *
     * @param token   身份验证token
     * @param cartIds 购物车ids
     * @param order   需要添加的订单信息
     * @return
     */
    @PostMapping("/add")
    @ApiOperation(value = "添加订单(手动方式实现)")
    @ApiImplicitParam(dataType = "string", name = "cartIds", value = "购物车ids", required = true)
    public ResponseResultVO add(@RequestHeader("token") String token,
                                                 String cartIds,
                                                 @RequestBody Orders order) {

        ResponseResultVO resultVO = null;
        try {
            resultVO = ordersService.addOrder(cartIds, order);
        } catch (Exception e) {
            logger.info("-----提交订单失败:[" + e.getMessage() + "]-----");
            resultVO = new ResponseResultVO(RespCode.FAILED.getCode(), "提交订单失败", null);
        }
        return resultVO;
    }


    /**
     * 接口作用：引入redisson框架作为分布式锁，解决加锁与释放锁各种场景问题
     * 业务流程分析：
     *      - 1.用户提交订单，调用该接口
     *      - 2.使用redission给购物车所有商品加锁(可指定锁的获取方式是公平锁还是非公平锁以及进行加锁是阻塞还是非阻塞的)
     *          - 2.1 如果加锁成功
     *              - 2.1.1 校验库存,如果库存充足
     *                  - (1）生成订单信息
     *                  - (2) 生成商品快照
     *                  - (3) 扣减库存
     *                  - (4) 删除购物车信息
     *                  - (5) 释放锁
     *              - 2.1.2 如果库存不足，不进行业务操作，返回空
     *          - 2.2 如果部分商品加锁失败
     *              - 2.2.1 释放已经加锁的商品，防止死锁
     *
     * @param token
     * @param cartIds
     * @param order
     * @return
     */
    @PostMapping("/addByRedisson")
    @ApiOperation(value = "添加订单(使用redisson方式实现)")
    @ApiImplicitParam(dataType = "string", name = "cartIds", value = "购物车ids", required = true)
    public ResponseResultVO addByRedisson(@RequestHeader("token") String token,
                                                 String cartIds,
                                                 @RequestBody Orders order) {

        ResponseResultVO resultVO = null;
        try {
            resultVO = ordersService.addOrderByRedisson(cartIds, order);
        } catch (Exception e) {
            logger.info("-----提交订单失败:[" + e.getMessage() + "]-----");
            resultVO = new ResponseResultVO(RespCode.FAILED.getCode(), "提交订单失败", null);
        }
        return resultVO;
    }


    /**
     * 接口作用：根据用户的id与订单状态查询订单信息(此时的商品信息是商品快照中的)
     * 业务流程分析：
     *      - 1.用户进入用户中心后，点击"我的订单"后，进入到订单列表页(可根据订单状态分为全部/待付款/待发货/待收货/待评价...)
     *      - 2.根据当前登录用户的id与订单状态去订单表中查询当前用户的订单信息，同时关联查询商品快照
     *
     * @param token
     * @param userId 用户id
     * @param status 订单状态
     * @param pageNum
     * @param limit
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("查询订单列表数据接口(手动使用limit分页)")
    @ApiImplicitParams({
        @ApiImplicitParam(dataType = "string", name = "userId", value = "用户ID", required = true),
        @ApiImplicitParam(dataType = "string", name = "status", value = "订单状态", required = false),
        @ApiImplicitParam(dataType = "int", name = "pageNum", value = "页码", required = true),
        @ApiImplicitParam(dataType = "int", name = "limit", value = "每页条数", required = true)
    })
    public ResponseResultVO list(@RequestHeader("token") String token,
                                 @RequestParam("userId") String userId,
                                 @RequestParam("status") String status,
                                 @RequestParam("pageNum") int pageNum,
                                 @RequestParam("limit") int limit) {
        ResponseResultVO resultVO = ordersService.listOrdersByUserIdAndStatus(userId, status, pageNum, limit);
        return resultVO;
    }


    @GetMapping("/listPage")
    @ApiOperation("查询订单列表数据接口(使用pageHelper插件分页)")
    @ApiImplicitParams({
        @ApiImplicitParam(dataType = "string", name = "userId", value = "用户ID", required = true),
        @ApiImplicitParam(dataType = "string", name = "status", value = "订单状态", required = false),
        @ApiImplicitParam(dataType = "int", name = "pageNum", value = "页码", required = true),
        @ApiImplicitParam(dataType = "int", name = "limit", value = "每页条数", required = true)
    })
    public ResponseResultVO listPage(@RequestHeader("token") String token,
                                     @RequestParam("userId") String userId,
                                     @RequestParam("status") String status,
                                     @RequestParam("pageNum") int pageNum,
                                     @RequestParam("limit") int limit) {
        ResponseResultVO resultVO = ordersService.listOrdersByUserIdAndStatusPage(userId, status, pageNum, limit);
        return resultVO;
    }


    /**
     * 接口作用：根据订单id逻辑删除订单(改变订单状态为"删除")
     * 业务流程分析：
     *      - 1.用户进入"我的订单"
     *      - 2.用户点击某条订单，点击"更多-删除订单"
     *      - 3.调用该接口，逻辑删除订单
     * 考虑点：逻辑删除状态默认为未删除，一旦调用该接口都是想去修改状态为删除，所有就不去从前端传状态参数过来了
     * @param token
     * @param orderId
     * @return
     */
    @GetMapping("/deleteOrder")
    @ApiOperation(value = "删除订单信息(使用逻辑删除状态)")
    @ApiImplicitParam(value = "订单id", name = "orderId", dataType = "string", required = true)
    public ResponseResultVO deleteOrder(@RequestHeader("token") String token,
                                        @RequestParam("orderId") String orderId) {

        ResponseResultVO respResultVO = ordersService.updateOrderDeleteStatus(orderId, Constants.DeleteStatus.DELETED.getValue());
        return respResultVO;
    }


    /**
     * 接口作用：根据订单id更新订单状态
     * 业务流程分析：
     *      - 1.在商家管理系统，商家发货，需要修改订单为"待收货"
     *      - 2.在用户订单中心(待收货列表),点击确认收货后，需要修改订单状态为"待评价"
     *      - 3.调用该接口，更新订单状态
     * 考虑点：订单状态有好几种修改方式，从前端传修改状态，复用接口
     * @param token
     * @param orderId
     * @return
     */
    @GetMapping("/updateOrder")
    @ApiOperation(value = "更新订单状态")
    @ApiImplicitParams({
        @ApiImplicitParam(value = "订单id", name = "orderId", dataType = "string", required = true),
        @ApiImplicitParam(value = "状态", name = "status", dataType = "string", required = true)
    })
    public ResponseResultVO updateOrder(@RequestHeader("token") String token,
                                        @RequestParam("orderId") String orderId,
                                        @RequestParam("status") String status) {

        int updateResult = ordersService.updateOrderStatus(orderId, status);
        if (updateResult > 1) {
            return new ResponseResultVO(RespCode.SUCCESS.getCode(), "success", null);
        } else {
            return new ResponseResultVO(RespCode.FAILED.getCode(), "fail", null);
        }
    }
}
